/*
 * Zombie Smasher 2
 * Hyperkani Oy, 2012
 * 
 * Tero Pyylampi
 *
 * A handle for processing touch events in the game layer
 */

#include "GameTouchHandle.h"
#include "../GameCamera.h"
#include "../../Common.h"

using namespace cocos2d;

// default constructor
GameTouchHandle::GameTouchHandle(  GameCamera *gameCamera, b2Body *groundBody ) 
	: mGameCamera( gameCamera ), mGroundBody( groundBody ), mTouch( 0 ), mTouchType( TOUCH_NONE ), 
	mAnotherHandle( 0 ), mCameraTreshold( WIN_WIDTH / 25.0f ), mTresholdReached( false ), 
	mZoomDirection( false ), mMouseJoint( 0 ), mHitBody( 0 ), mInvalidBombPosition( false ) {
}

// default destructor
GameTouchHandle::~GameTouchHandle() {}

// start moving camera with a single finger
void GameTouchHandle::startMovingCamera( cocos2d::CCTouch *touch, cocos2d::CCLayer *layer ) {
	mTouch = touch;
	mTouchType = TOUCH_CAMERA_SINGLE;
	mStartingPos = touch->locationInView();
	mStartingPos = CCDirector::sharedDirector()->convertToGL( mStartingPos );
	mStartingPos = layer->convertToNodeSpace( mStartingPos );
}

// start moving camera / zooming event with two fingers
void GameTouchHandle::startCameraTouch( cocos2d::CCTouch *touch, cocos2d::CCLayer *layer ) {
	mTouch = touch;
	mTouchType = TOUCH_CAMERA_MULTI;
	mStartingPos = touch->locationInView();
	mStartingPos = CCDirector::sharedDirector()->convertToGL( mStartingPos );
	mStartingPos = layer->convertToNodeSpace( mStartingPos );
}

// start moving an object
void GameTouchHandle::startObjectTouch( cocos2d::CCTouch *touch, cocos2d::CCLayer *layer, b2Body *body, b2World *world ) {
	mTouch = touch;
	mTouchType = TOUCH_PHYSICS_OBJECT;
	mStartingPos = touch->locationInView();
	mStartingPos = CCDirector::sharedDirector()->convertToGL( mStartingPos );
	mStartingPos = layer->convertToNodeSpace( mStartingPos );
	
	mHitBody = body;
	// with dynamic bodies we can use mouse joint
	if( mHitBody->GetType() == b2_dynamicBody ) {
		// set mousejoint parameters
		b2MouseJointDef mDef;
		mDef.bodyA = mGroundBody;
		mDef.bodyB = mHitBody;
		mDef.collideConnected = true;	
		mDef.target.Set( body->GetPosition().x, body->GetPosition().y );
		mDef.maxForce = mHitBody->GetMass() * 10000.0f;
		// create mousejoint
		mMouseJoint = dynamic_cast< b2MouseJoint* >( world->CreateJoint( &mDef ) );
		mHitBody->SetAwake( true );
	}
	// kinematic bodies must be moved in other ways
	else {
		// TODO ?
	}

	// reset this state to true to prevent accidents
	mInvalidBombPosition = true;
}

// convert this event to the multi camera slave
void GameTouchHandle::resetToMultiCameraSlave() {
	mTouchType = TOUCH_CAMERA_MULTI;
	mAnotherHandle = 0;
}

// update this touch handle
void GameTouchHandle::update( cocos2d::CCTouch *touch, cocos2d::CCLayer *layer ) {
	mTouch = touch;

	switch( mTouchType ) {

		// camera event (move) with a single finger
		case TOUCH_CAMERA_SINGLE: {
			// locations of the camera
			CCPoint loc = mTouch->locationInView();
			loc = CCDirector::sharedDirector()->convertToGL( loc );
			loc = layer->convertToNodeSpace( loc );
			CCPoint locold = mTouch->previousLocationInView();
			locold = CCDirector::sharedDirector()->convertToGL( locold );
			locold = layer->convertToNodeSpace( locold );

			// check whether we have reached the treshold
			if( mTresholdReached == false ) {
				if( distanceBetweenPoints( mStartingPos, loc ) >= mCameraTreshold / 4.0f )
					mTresholdReached = true;
			}
			// normally move camera
			else {
				CCPoint cameraSpeed = ccp( loc.x - locold.x, loc.y - locold.y);
				cameraSpeed.x *= mGameCamera->getCurrentZoomLevel() * 2.2f;
				cameraSpeed.y *= mGameCamera->getCurrentZoomLevel() * 2.2f;
				mGameCamera->moveCamera( cameraSpeed );
				// if the speed was great, we can assume that it was supposed to accelerate camera
				if( distanceBetweenPoints( loc, locold ) >= mCameraTreshold / 4.0f )
					mGameCamera->setCameraSpeed( cameraSpeed );
			}
		}
		break;

		// camera event
		case TOUCH_CAMERA_MULTI: {
			// only continue if the another handler has been set and also moving camera
			if( mAnotherHandle != 0 && mAnotherHandle->getCurrentType() == TOUCH_CAMERA_MULTI ) {
				// location of the first camera
				CCPoint loc1 = mTouch->locationInView();
				loc1 = CCDirector::sharedDirector()->convertToGL( loc1 );
				loc1 = layer->convertToNodeSpace( loc1 );
				// location of the second camera
				CCPoint loc2 = mAnotherHandle->mTouch->locationInView();
				loc2 = CCDirector::sharedDirector()->convertToGL( loc2 );
				loc2 = layer->convertToNodeSpace( loc2 );

				// old locations of the first camera
				CCPoint loc1old = mTouch->previousLocationInView();
				loc1old = CCDirector::sharedDirector()->convertToGL( loc1old );
				loc1old = layer->convertToNodeSpace( loc1old );
				// old locations of the second camera
				CCPoint loc2old = mAnotherHandle->mTouch->previousLocationInView();
				loc2old = CCDirector::sharedDirector()->convertToGL( loc2old );
				loc2old = layer->convertToNodeSpace( loc2old );

				// possibly moving camera
				if( std::abs( distanceBetweenPoints( loc1, loc2 ) - distanceBetweenPoints( mStartingPos, mAnotherHandle->mStartingPos ) ) < mCameraTreshold ) {
					// calculate values
					CCPoint cameraSpeed = ccp( ( ( loc1.x - loc1old.x ) + ( loc2.x - loc2old.x ) ) / 2.0f, 
						( ( loc1.y - loc1old.y ) + ( loc2.y - loc2old.y ) ) / 2.0f );
					cameraSpeed.x *= mGameCamera->getCurrentZoomLevel() * 2.6f;
					cameraSpeed.y *= mGameCamera->getCurrentZoomLevel() * 2.6f;
					mGameCamera->setCameraSpeed( cameraSpeed );
				}

				// or zooming
				else {
					// check treshold
					if( mTresholdReached == false ) {
						// check if the treshold has been reached
						if( distanceBetweenPoints( loc1, mStartingPos ) + distanceBetweenPoints( loc2, mAnotherHandle->mStartingPos ) > mCameraTreshold ) {
							mTresholdReached = true;
							if( distanceBetweenPoints( loc1, loc2 ) > distanceBetweenPoints( mStartingPos, mAnotherHandle->mStartingPos ) )
								mZoomDirection = true;
							else
								mZoomDirection = false;
						}
					}
					// normally zooming
					else {
						// calculate values
						float distanceBetweenCurrentPoints = distanceBetweenPoints( loc1, loc2 );
						float distanceBetweenLastPoints = distanceBetweenPoints( loc1old, loc2old );

						// possibly reset (moving towards the "wrong" direction => reset treshold)
						if( distanceBetweenCurrentPoints > distanceBetweenLastPoints && mZoomDirection == false ) {
							mStartingPos = CCPoint( loc1 );
							mAnotherHandle->mStartingPos = CCPoint( loc2 );
							mTresholdReached = false;
						}
						else if( distanceBetweenCurrentPoints < distanceBetweenLastPoints && mZoomDirection == true ) {
							mStartingPos = CCPoint( loc1 );
							mAnotherHandle->mStartingPos = CCPoint( loc2 );
							mTresholdReached = false;
						}
						// perform a zoom event
						else {
							CCPoint zoomPos( ( loc1.x + loc2.x ) / 2, ( loc1.y + loc2.y ) / 2 );
							mGameCamera->setZoomSpeed( ( distanceBetweenCurrentPoints - distanceBetweenLastPoints ) / 70.0f, zoomPos );
						}
					}
				}
			}
		}
		break;

		// moving something, update mousejoint
		case TOUCH_PHYSICS_OBJECT: {
			// get location
			CCPoint touchLocation = touch->locationInView();
			// convert to node space
			touchLocation = CCDirector::sharedDirector()->convertToGL( touchLocation );
			touchLocation = layer->convertToNodeSpace( touchLocation );
			// convert to world location
			b2Vec2 screenLocation = b2Vec2( touchLocation.x, touchLocation.y );
			b2Vec2 worldLocation = mGameCamera->convertScreenToWorld( screenLocation );
			if( mHitBody->GetType() == b2_dynamicBody ) {
				// set mousejoint target position
				mMouseJoint->SetTarget( worldLocation );
			}
			else {
				mHitBody->SetTransform( worldLocation, mHitBody->GetAngle() );
			}
		}
		break;

	};
}

// reset this touch handle
void GameTouchHandle::reset() {
	mTouch = 0;
	mTouchType = TOUCH_NONE;
	mStartingPos = ccp( 0, 0 );

	// possibly reset another handle
	if( mAnotherHandle != 0 )
		mAnotherHandle->setAnotherTouchHandle( 0 );
	mAnotherHandle = 0;

	mTresholdReached = false;

	// destroy mouse joint
	if( mMouseJoint != 0 )
		mMouseJoint->GetBodyA()->GetWorld()->DestroyJoint( mMouseJoint );
	// normally reset variables
	mMouseJoint = 0;
	mHitBody = 0;
}

// release current object touch
void GameTouchHandle::releaseObject() {
	// release physics object
	if( mTouchType == TOUCH_PHYSICS_OBJECT ) {
		// destroy mouse joint
		if( mMouseJoint != 0 )
			mMouseJoint->GetBodyA()->GetWorld()->DestroyJoint( mMouseJoint );
		// normally reset variables
		mMouseJoint = 0;
		mHitBody = 0;
	}
}

// set another handle
void GameTouchHandle::setAnotherTouchHandle( GameTouchHandle *anotherHandle ) {
	mAnotherHandle = anotherHandle;
}

// get touch
cocos2d::CCTouch *GameTouchHandle::getTouch() {
	return mTouch;
}

// get current mode
TouchType GameTouchHandle::getCurrentType() {
	return mTouchType;
}

// get hit body
b2Body *GameTouchHandle::getHitBody() {
	return mHitBody;
}

// whether the treshold has been reached
bool GameTouchHandle::tresholdReached() {
	return mTresholdReached;
}

// get starting pos in world coords
b2Vec2 GameTouchHandle::getStartingPosInWorldCoords() {
	b2Vec2 screenLoc( mStartingPos.x, mStartingPos.y );
	return /*mGameCamera->convertScreenToWorld( screenLoc );*/ b2Vec2();
}

// set invalid bomb position
void GameTouchHandle::setInvalidBombPosition( bool state ) {
	mInvalidBombPosition = state;
}

// get invalid bomb position
bool GameTouchHandle::getInvalidBombPosition() {
	return mInvalidBombPosition;
}

// calculates the distance between two points
float GameTouchHandle::distanceBetweenPoints( cocos2d::CCPoint &p1, cocos2d::CCPoint &p2 ) {
	return std::sqrt( std::pow( p2.x - p1.x, 2 ) + std::pow( p2.y - p1.y, 2 ) );
}
